package com.datalayer.orm;

import com.container.app.annotations.Component;
import com.datalayer.annotations.Entity;
import com.datalayer.testclasses.Angajat;
import com.datalayer.testclasses.Departament;
import com.datalayer.testclasses.Functie;
import com.datalayer.utils.OrmProcessing;
import com.datalayer.utils.Pair;

import java.io.IOException;
import java.lang.annotation.AnnotationTypeMismatchException;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * User: Georgiana.Scarlat
 * Date: 7/3/12
 * Time: 12:42 PM
 */
@Component
public class MyQuery implements DBQuery {

    private String myORMQuery;
    private MyORM orm;

    private List<String> keyWords;

    protected MyQuery(String myORMQuery, MyORM orm) {
        this.myORMQuery = myORMQuery;
        this.orm = orm;

        keyWords = new LinkedList<String>();
        keyWords.add("FROM");
        keyWords.add("SELECT");
        keyWords.add("WHERE");
        keyWords.add("GROUP");
        keyWords.add("ORDER");
        keyWords.add("UPDATE");
        keyWords.add("SET");
    }

    /**
     * Obtains the SQL query equivalent to this MyQuery query.
     *
     * @return SQL query equivalent to this MyQuery query
     */
    public String getSQLQuery() throws SQLException, IllegalArgumentException, IncompleteAnnotationException {

        return processQuery(splitInParts());
    }

    /**
     * Obtains a list of objects that represent the rows from the query results.
     * <p/>
     * The objects can be of multiple types when the query result contains columns from multiple tables.
     *
     * @return list of objects that represent the rows from the query results
     */
    @SuppressWarnings("unchecked")
    public List getQueryResults() throws UnsupportedOperationException, IncompleteAnnotationException, AnnotationTypeMismatchException {

        List objects = new LinkedList();
        List<Class> usedClasses = classesInQuery();

        try {

            ResultSet resultSet = orm.getStatement().executeQuery(getSQLQuery());
            HashMap<Integer, Pair<Pair<String, String>, String>> tableColumnAssoc = OrmProcessing.getTableColumnAssociation(resultSet);

            for (Class cls : usedClasses) {

                HashMap<Pair<String, String>, Integer> indexes = OrmProcessing.getOwnColumIndexes(cls, tableColumnAssoc);

                while (resultSet.next()) {

                    Constructor constructor = cls.getDeclaredConstructor();
                    constructor.setAccessible(true);
                    Object object = constructor.newInstance();

                    for (Map.Entry<Pair<String, String>, Integer> entry : indexes.entrySet()) {
                        Integer index = entry.getValue();
                        Pair<String, String> name_type = entry.getKey();
                        OrmProcessing.setColumnValueForObject(object, new Pair<String, Object>(name_type.getFirst(), resultSet.getObject(index)));
                    }
                    objects.add(object);
                }

                resultSet.beforeFirst();

            }

            return objects;
        } catch (SQLException e) {
            throw new UnsupportedOperationException("Can't get results from query.",e);
        } catch (InvocationTargetException e) {
            throw new UnsupportedOperationException("The constructor can't be invoked.",e);
        } catch (NoSuchMethodException e) {
            throw new UnsupportedOperationException("There is no default constructor.",e);
        } catch (InstantiationException e) {
            throw new UnsupportedOperationException("Can't instantiate the class.",e);
        } catch (IllegalAccessException e) {
            throw new UnsupportedOperationException("The constructor is not accessible.",e);
        }

    }

    public <T> List<T> getQueryHomogeneousResults(Class<T> resultType) throws UnsupportedOperationException, AnnotationTypeMismatchException {
        List<T> objects = new LinkedList<T>();
        try {

            ResultSet resultSet = orm.getStatement().executeQuery(getSQLQuery());
            int columnNo = resultSet.getMetaData().getColumnCount();
            T object;

            while (resultSet.next()) {

                Constructor<T> constructor = resultType.getConstructor();
                constructor.setAccessible(true);
                object = constructor.newInstance();
                for (int index = 1; index <= columnNo; index++) {

                    OrmProcessing.setColumnValueForObject(object, new Pair<String, Object>(resultSet.getMetaData().getColumnLabel(index), resultSet.getObject(index)));
                }
                objects.add(object);
            }

            return objects;
        } catch (SQLException e) {
            throw new UnsupportedOperationException("Can't get results from query.",e);
        } catch (InvocationTargetException e) {
            throw new UnsupportedOperationException("The constructor can't be invoked.",e);
        } catch (NoSuchMethodException e) {
            throw new UnsupportedOperationException("There is no default constructor.",e);
        } catch (InstantiationException e) {
            throw new UnsupportedOperationException("Can't instantiate the class.",e);
        } catch (IllegalAccessException e) {
            throw new UnsupportedOperationException("The constructor is not accessible.",e);
        }


    }

    /**
     * If the query actually represents an update command, make the corresponding update.
     */
    public void makeUpdate() throws SQLException, IllegalArgumentException, IncompleteAnnotationException {

        orm.getStatement().executeUpdate(getSQLQuery());

    }


    /* Auxiliary private methods used to implement main features*/

    /**
     * Split Query String in semantic parts, delimited by key words: from, select, etc.
     *
     * @return list of lists of words from an independent command part
     */
    private List<List<String>> splitInParts() {

        String words[] = myORMQuery.split("\\s+");

        List<List<String>> parts = new LinkedList<List<String>>();
        List<String> part = null;

        for (String word : words) {

            if (keyWords.contains(word.toUpperCase())) {
                if (part != null)
                    parts.add(part);
                part = new LinkedList<String>();
                part.add(word);

            } else {
                if (part != null) {
                    part.add(word);

                }
            }
        }
        parts.add(part);

        return parts;
    }


    /**
     * Obtains the associations between the class, class name, table name associated with the class
     * and it's alias in the command from a 'from' clause of an MyQuery query.
     *
     * @param fromClause 'from' clause
     * @return the association as a hashmap
     */
    private HashMap<Pair<String, String>, Pair<String, Class>> getNameAssociation(List<String> fromClause) throws SQLException, IllegalArgumentException, IncompleteAnnotationException {


        HashMap<Pair<String, String>, Pair<String, Class>> associations = new HashMap<Pair<String, String>, Pair<String, Class>>();
        List<Pair<String, String>> class_alias = OrmProcessing.getNameAndAlias(fromClause);

        try {

            for (Pair<String, String> aliasPar : class_alias) {
                Class clas = Class.forName(aliasPar.getFirst());
                String tableName = (String) OrmProcessing.getAnnotationValue(1, clas, Entity.class, "tableName");
                if (tableName == null)
                    throw new IllegalArgumentException("Query Syntax is wrong");
                orm.createTableIfDoesNotExist(clas);
                associations.put(aliasPar, new Pair<String, Class>(tableName, clas));
            }

            return associations;
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Referred class doesn't exist ",e);
        } catch (NoSuchMethodException e) {
            throw new IncompleteAnnotationException(Entity.class, "tableName");
        } catch (IllegalAccessException e) {
            throw new IncompleteAnnotationException(Entity.class, "tableName");
        } catch (InvocationTargetException e) {
            throw new IncompleteAnnotationException(Entity.class, "tableName");
        }

    }


    /**
     * Processes a MyQuery query into an SQL query.
     *
     * @param allParts the MyQuery query inthe form of list of parts
     * @return a String representing the SQL query
     */

    private String processQuery(List<List<String>> allParts) throws SQLException, IllegalArgumentException, IncompleteAnnotationException {
        String sqlQuery = "";

        List<String> words = new LinkedList<String>();
        for (List<String> part : allParts) {
            for (String elem : part) {
                words.add(elem);

            }
        }
        words = OrmProcessing.replaceInQuery(getNameAssociation(OrmProcessing.getDeclaringClause(allParts)), words);
        for (String w : words) {
            sqlQuery += w + " ";
        }

        return sqlQuery;
    }


    /**
     * Obtains a list of classes that are mentioned in the MyQuery query.
     *
     * @return list of classes that are mentioned in the MyQuery query
     */
    private List<Class> classesInQuery() {
        List<Class> classes = new LinkedList<Class>();

        try {

            HashMap<Pair<String, String>, Pair<String, Class>> associations = getNameAssociation(OrmProcessing.getDeclaringClause(splitInParts()));
            for (Map.Entry<Pair<String, String>, Pair<String, Class>> entry : associations.entrySet()) {
                classes.add(entry.getValue().getSecond());
            }
            return classes;
        } catch (SQLException e) {
            throw new UnsupportedOperationException("Can't get classes from query.",e);
        }

    }


    public static void main(String[] args) throws SQLException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, IOException {

        MyORM orm = new MyORM();


        orm.initConnection("jdbc:mysql://localhost:3306/MyDB", "monty", "x");
        orm.createTableIfDoesNotExist(Angajat.class);
        Functie functie = new Functie(101);
        Departament departament = new Departament(123, "IT");
        Angajat angajat = new Angajat(departament, functie, 400, "LILI");

        orm.insertObject2DB(angajat);
        MyQuery query = orm.makeQuery("select  A.nume , A.functie , F.id from  com.datalayer.testclasses.Functie F , com.datalayer.testclasses.Angajat A where A.functie = F.id order by A.nume");


        String qu = query.getSQLQuery();
        System.out.println("SQL Query: ");
        System.out.println(qu);


        List results = query.getQueryResults();
        System.out.println();
        System.out.println("Query Results:");

        for (Object obj : results) {
            System.out.println(obj);
        }

        query = orm.makeQuery("select A.id , A.nume , D.id from com.datalayer.testclasses.Departament D , com.datalayer.testclasses.Angajat A where A.id > 100 and D.nume = 'IT' order by D.id");
        qu = query.getSQLQuery();
        System.out.println();
        System.out.println("SQL Query: ");
        System.out.println(qu);


        results = query.getQueryResults();
        System.out.println();
        System.out.println("Query Results:");


        for (Object obj : results) {
            System.out.println(obj);
        }
        orm.closeConnection();


    }


}
